#include "DockWidgetSubsystem.h"

#include "DockLayout.h"
#include "Math/Rect.h"
#include "Framework/SubsystemManager.h"
#include "Widgets/WindowManager.h"
#include "Widgets/Window.h"

#include "DockableWindow.h"

#include "File/JsonUtils.h"


static RDockWidgetSubsystem *Instance=nullptr;

void RDockWidgetSubsystem::Initialize()
{
    //make sure window subsystem is initialized
    GetApplication()->MakeSureInitialized<RWindowManager>();
    
    Instance=this;
}

void RDockWidgetSubsystem::Deinitialize()
{
    Instance=nullptr;
}

RDockWidgetSubsystem *RDockWidgetSubsystem::Get()
{
    return Instance;
}

void RDockWidgetSubsystem::RegisterDockWidget(
    const CString & LayoutGroupName,
        const CString & UniqueWidgetID,
        const CString & Title,
        TDelegate<TSharedObjectPtr<RWidget>,RWidget* /*Parent widget*/ > InWidgetCreator
        )
{
    auto& Group = DockWidgetCreators.FindOrAdd(LayoutGroupName);

    SDockWidgetCreateInfo WidgetCreateInfo;
    WidgetCreateInfo.UniqueWidgetID=UniqueWidgetID;
    WidgetCreateInfo.Title=Title;
    WidgetCreateInfo.WidgetCreator=InWidgetCreator;
    WidgetCreateInfo.LayoutGroupName=LayoutGroupName;
    
    Group.Add(UniqueWidgetID, WidgetCreateInfo);
}


TSharedObjectPtr<RWidget> RDockWidgetSubsystem::CreateDockWidget(const CString &LayoutGroupName, const CString &UniqueWidgetName, TSharedObjectPtr<RWidget> InParentWidget)
{
    assert(DockWidgetCreators.Contains(LayoutGroupName) && "Dock widget group not found,please register first");
    
    auto& Group = DockWidgetCreators[LayoutGroupName];
    assert(Group.Contains(UniqueWidgetName)&& "Dock widget not found,please register first");
 
    return Group[UniqueWidgetName].WidgetCreator.Execute(InParentWidget.Get());
}

CString  RDockWidgetSubsystem::GetDockWidgetTitle(const CString & LayoutGroupName,const CString & UniqueWidgetName)
{
    assert(DockWidgetCreators.Contains(LayoutGroupName) && "Dock widget group not found,please register first");
    
    auto& Group = DockWidgetCreators[LayoutGroupName];
    assert(Group.Contains(UniqueWidgetName)&& "Dock widget not found,please register first");
 
    return Group[UniqueWidgetName].Title;
}

//create json from a layout
std::shared_ptr<CJsonNodeBase> CreateJson(TSharedObjectPtr<RDockLayout> InLayout)
{
    auto LayoutJson=std::make_shared<CJsonObjectNode>();

    CEnumBase* EnumType=(CEnumBase*)CGetType<EDockLayoutDirection>()();
    LayoutJson->Add(U"Direction",std::make_shared<CJsonStringNode>(EnumType->GetValueName(InLayout->GetDirection())));

    if(InLayout->GetDirection()==EDockLayoutDirection::Stacked)
    {
        LayoutJson->Add(U"SelectedIndex",std::make_shared<CJsonIntNode>(InLayout->GetSelectedIndexInStack()));
    }

    std::shared_ptr<CJsonVectorNode> DockedWidgets= std::make_shared<CJsonVectorNode>();
    LayoutJson->Add(U"DockedWidgets",DockedWidgets);
    for(int32_t i=0;i<InLayout->GetDockedWidgetsAndLayoutsNum();i++)
    {
        auto DockedWidget= InLayout->GetDockedWidgetsOrLayout(i);
        float SizeWeight= InLayout->GetSizeWeight(i);

        std::shared_ptr<CJsonNodeBase> DockedWidgetJson;
        if(DockedWidget->IsA<RDockLayout>())
        {
            auto DockLayout=RDockLayoutPtr(DockedWidget);
            auto SubLayoutJson= std::make_shared<CJsonObjectNode>();
            SubLayoutJson->Add(U"Layout",CreateJson(DockLayout));
            SubLayoutJson->Add(U"SizeWeight",std::make_shared<CJsonFloatNode>(SizeWeight));

            DockedWidgetJson= std::static_pointer_cast<CJsonNodeBase>(SubLayoutJson);
        }
        else
        {
            auto WidgetJson=std::make_shared<CJsonObjectNode>();
            WidgetJson->Add(U"SizeWeight",std::make_shared<CJsonFloatNode>(SizeWeight));
            WidgetJson->Add(U"WidgetName",std::make_shared<CJsonStringNode>(InLayout->GetDockedWidgetName(i)));

            DockedWidgetJson= std::static_pointer_cast<CJsonNodeBase>(WidgetJson);
        }

        DockedWidgets->Add(DockedWidgetJson);

    }

    return LayoutJson;
}

//create layout from json
TSharedObjectPtr<RDockLayout> CreateLayout(std::shared_ptr<CJsonNodeBase> InLayoutJson,TSharedObjectPtr<RWidget> InParentWidget)
{
    auto Result=NewWidget<RDockLayout>(InParentWidget);

    auto LayoutJson= std::static_pointer_cast<CJsonObjectNode>(InLayoutJson);
    
    //get direction 
    CEnumBase* EnumType=(CEnumBase*)CGetType<EDockLayoutDirection>()();
    Result->SetDirection((EDockLayoutDirection)EnumType->GetValue( LayoutJson->Get(U"Direction")->ToString()->GetValue()));
   
    if(Result->GetDirection()==EDockLayoutDirection::Stacked)
    {
        Result->SetSelectedIndexInStack(LayoutJson->Get(U"SelectedIndex")->ToInt()->GetValue());
    }

    //get docked widgets
    std::shared_ptr<CJsonVectorNode> DockedWidgets= std::static_pointer_cast<CJsonVectorNode>(LayoutJson->Get(U"DockedWidgets"));

    for(auto& DockedWidgetJson: DockedWidgets->GetNodes())
    {
        std::shared_ptr<CJsonObjectNode> WidgetJson= std::static_pointer_cast<CJsonObjectNode>(DockedWidgetJson);
        float SizeWeight= WidgetJson->Get(U"SizeWeight")->ToFloat()->GetValue();
        if(WidgetJson->Contains(U"Layout"))
        {
            auto SubLayoutJson= std::static_pointer_cast<CJsonObjectNode>(WidgetJson->Get(U"Layout"));
            auto SubLayout=CreateLayout(SubLayoutJson,Result);
            Result->DockLayout(SubLayout,SizeWeight);
        }
        else
        {
            auto WidgetName=WidgetJson->Get(U"WidgetName")->ToString()->GetValue();
            Result->DockWidget(WidgetName,SizeWeight);
        }
    }

    return Result;
}

CString RDockWidgetSubsystem::SaveDockLayouts(TSharedObjectPtr<RDockLayout> RootWidgetOfMainWindow)
{
    CString GroupName= RootWidgetOfMainWindow->GetGroupName();

    TVector<RDockableWindowPtr> AllDockableWindows={};
    //find all root layout with same group name
    auto WindowManager=GetApplication()->GetSubsystem<RWindowManager>();
    for(auto Window:WindowManager->GetWindows())
    {
        if(Window->IsA<RDockableWindow>())
        {
            auto DockableWindow=RDockableWindowPtr(Window);
            if(DockableWindow->GetRootLayout()->GetGroupName()==GroupName)
            {
                AllDockableWindows.Add(DockableWindow);
            }
        }
    }

    auto RootJson=std::make_shared<CJsonObjectNode>();
    RootJson->Add(U"GroupName",std::make_shared<CJsonStringNode>(GroupName));

    if(RootWidgetOfMainWindow)
    {
        RootJson->Add(U"MainWindowLayout",CreateJson(RootWidgetOfMainWindow));
    }

    auto DockableWindowsJson=std::make_shared<CJsonVectorNode>();
    RootJson->Add(U"DockableWindows",DockableWindowsJson);

    for(auto& Window: AllDockableWindows)
    {
        std::shared_ptr<CJsonObjectNode> WindowJson=std::make_shared<CJsonObjectNode>();
        WindowJson->Add(U"Layout",CreateJson(Window->GetRootLayout()));
        WindowJson->Add(U"PositionX", std::make_shared<CJsonIntNode>(Window->GetWindowPosition().X));
        WindowJson->Add(U"PositionY", std::make_shared<CJsonIntNode>(Window->GetWindowPosition().Y));
        WindowJson->Add(U"Width", std::make_shared<CJsonIntNode>(Window->GetWindowSize().X));
        WindowJson->Add(U"Height", std::make_shared<CJsonIntNode>(Window->GetWindowSize().Y));

        DockableWindowsJson->Add(WindowJson);
    }

    return CJsonUtils::ToString(RootJson);
}

TSharedObjectPtr<RDockLayout> RDockWidgetSubsystem::LoadDockLayoutGroup(
    RWidgetPtr InParentWidget
    ,const CString &InLayoutGroupString)
{
    std::shared_ptr<CJsonObjectNode> RootJson=
        std::static_pointer_cast<CJsonObjectNode>(CJsonUtils::Parse(InLayoutGroupString));

    CString SaveGroupName= RootJson->Get(U"GroupName")->ToString()->GetValue();
    //assert(SaveGroupName==InRootWidgetOfMainWindow->GetGroupName() && "Group name not match");



    //first layout is root layout of main window
    TSharedObjectPtr<RDockLayout> RootWidgetOfMainWindow;
    if(RootJson->Contains(U"MainWindowLayout"))
    {
        auto MainWindowLayoutJson=std::static_pointer_cast<CJsonObjectNode>(RootJson->Get(U"MainWindowLayout"));
        RootWidgetOfMainWindow=CreateLayout(MainWindowLayoutJson,InParentWidget);
    }
    
    std::shared_ptr<CJsonVectorNode> WindowsJson=
        std::static_pointer_cast<CJsonVectorNode>(RootJson->Get(U"DockableWindows"));

    for(auto& WindowJson: WindowsJson->GetNodes())
    {
        std::shared_ptr<CJsonObjectNode> WindowJsonDict=std::static_pointer_cast<CJsonObjectNode>(WindowJson);

        //Create dockable window
        auto DockableWindow=NewObject<RDockableWindow>();

        TSharedObjectPtr<RDockLayout> RootLayout=CreateLayout(
            std::static_pointer_cast<CJsonObjectNode>(WindowJsonDict->Get(U"Layout")),DockableWindow->GetParentOfRootLayout());
        DockableWindow->SetRootLayout(RootLayout);

        SIntPoint WindowPosition;
        WindowPosition.X=WindowJsonDict->Get(U"PositionX")->ToInt()->GetValue();
        WindowPosition.Y=WindowJsonDict->Get(U"PositionY")->ToInt()->GetValue();
        DockableWindow->SetWindowPosition(WindowPosition);

        SIntPoint WindowSize;
        WindowSize.X=WindowJsonDict->Get(U"Width")->ToInt()->GetValue();
        WindowSize.Y=WindowJsonDict->Get(U"Height")->ToInt()->GetValue();
        DockableWindow->SetWindowSize(WindowSize);
    }

    return RootWidgetOfMainWindow;
}


RDockLayoutPtr RDockWidgetSubsystem::FindDockLayoutUnderMouse(const CString& InLayoutGroupName ,RWindowPtr ExcludeWindow)
{
    auto MouseScreenPosition=SMouseButtonEvent::GetMosuePositionOnScreen().ToVec2();
    auto WindowManager=GetApplication()->GetSubsystem<RWindowManager>();
    
    for(auto Window:WindowManager->GetWindows())
    {
        if(Window==ExcludeWindow)
        {
            continue;
        }
        
        SRect WindowRect(Window->GetWindowPosition().ToVec2(),Window->GetWindowSize().ToVec2());
        if(!WindowRect.IsPointIn(MouseScreenPosition))
        {
            continue;
        }

        //iterial all widgets
        RDockLayoutPtr CurrentDockLayout=nullptr;
        TVector<RWidgetPtr> Widgets={Window->GetRootWidget()};
        while(!Widgets.Empty())
        {
            auto Widget=Widgets.Pop(false);
            
            
            if(Widget->IsA<RDockLayout>() )
            {
                auto Layout=RDockLayoutPtr(Widget);
                if(Layout->GetGroupName()!=InLayoutGroupName)
                {
                    //ignore dock layout which is not in group
                    continue;
                }

                SRect WidgetRect(Widget->GetPresentAbsolutePosition()+WindowRect.Min,Widget->GetPresentSize());
                if(!WidgetRect.IsPointIn(MouseScreenPosition))
                {
                    //ignore dock layout which is not under mouse
                    continue;
                }
                else
                {
                    CurrentDockLayout= Layout;
                    Widgets= CurrentDockLayout->GetDockedWidgetsAndLayouts();
                }
            }
            else
            {
                Widgets.Append(Widget->GetSubWidgets());
            }

        }

        if(CurrentDockLayout)
        {
            return CurrentDockLayout;
        }
    }
    
    return nullptr;
}
